All files / src/app/api/proxy proxyUtils.ts

0% Statements 0/69
0% Branches 0/69
0% Functions 0/13
0% Lines 0/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                                                                                                                                                                                                                       
const DEFAULT_ALLOWED_HOSTS_DEV = '*';
 
function parseBoolEnv(key: string, defaultValue: boolean): boolean {
  const raw = process.env[key];
  if (raw == null) return defaultValue;
  const v = String(raw).trim().toLowerCase();
  return v === '1' || v === 'true' || v === 'yes' || v === 'y' || v === 'on';
}
 
export function requireAuthEnabled(): boolean {
  return parseBoolEnv('STREAMCORE_PROXY_REQUIRE_AUTH', process.env.NODE_ENV !== 'development');
}
 
export function allowPrivateEnabled(): boolean {
  return parseBoolEnv('STREAMCORE_PROXY_ALLOW_PRIVATE', process.env.NODE_ENV === 'development');
}
 
export function getAllowedHosts(): string[] {
  // Default to allowing all hosts since this is a streaming platform
  // that needs to proxy content from various CDNs and sources
  const raw = process.env.STREAMCORE_PROXY_ALLOWED_HOSTS || '*';
 
  const list = String(raw)
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean);
 
  return list;
}
 
export function hasAuth(req: Request): boolean {
  const auth = req.headers.get('authorization') || '';
  if (auth.toLowerCase().startsWith('bearer ')) return true;
 
  const cookie = req.headers.get('cookie') || '';
  // Simple cookie lookup to avoid deps
  return cookie.split(';').some((part) => part.trim().startsWith('iptv_auth_token='));
}
 
function isIpV4(hostname: string): boolean {
  return /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname);
}
 
function ipV4ToBytes(ip: string): number[] | null {
  const parts = ip.split('.').map((p) => Number(p));
  if (parts.length !== 4) return null;
  if (parts.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
  return parts;
}
 
function isPrivateIpV4(ip: string): boolean {
  const b = ipV4ToBytes(ip);
  if (!b) return false;
  const [a, c] = b;
  // 10.0.0.0/8
  if (a === 10) return true;
  // 127.0.0.0/8 (loopback)
  if (a === 127) return true;
  // 169.254.0.0/16 (link-local)
  if (a === 169 && c === 254) return true;
  // 172.16.0.0/12
  if (a === 172 && c >= 16 && c <= 31) return true;
  // 192.168.0.0/16
  if (a === 192 && c === 168) return true;
  // 0.0.0.0/8, 100.64.0.0/10 (CGNAT)
  if (a === 0) return true;
  if (a === 100 && c >= 64 && c <= 127) return true;
  return false;
}
 
export function validateUpstreamUrl(urlParam: string): { ok: true; url: URL } | { ok: false; error: string; status: number } {
  let url: URL;
  try {
    url = new URL(urlParam);
  } catch {
    return { ok: false, error: 'Invalid url param', status: 400 };
  }
 
  if (!/^https?:$/.test(url.protocol)) {
    return { ok: false, error: 'Only http/https URLs are allowed', status: 400 };
  }
 
  if (url.username || url.password) {
    return { ok: false, error: 'Credentials in URL are not allowed', status: 400 };
  }
 
  const hostname = url.hostname.toLowerCase();
  if (hostname === 'localhost' || hostname.endsWith('.localhost')) {
    return { ok: false, error: 'Localhost is not allowed', status: 403 };
  }
 
  if (isIpV4(hostname) && isPrivateIpV4(hostname) && !allowPrivateEnabled()) {
    return { ok: false, error: 'Private IP ranges are not allowed', status: 403 };
  }
 
  const allowed = getAllowedHosts();
  if (allowed.length === 0) {
    return { ok: false, error: 'Proxy is disabled (no allowlist configured)', status: 403 };
  }
 
  if (!allowed.includes('*') && !allowed.includes(hostname)) {
    return { ok: false, error: `Host not allowed: ${hostname}`, status: 403 };
  }
 
  return { ok: true, url };
}